from marshmallow import Schema, fields
from vsogame.extensions import marshmallow as  ma
from vsogame.models.currency import Currency

import vsogame.schema.json as _json


class Success(ma.Schema):
    def jsonify(self, data, many=None, *args, **kwargs):

        rv = None
        if many is None:
            rv = super().jsonify(data, *args, **kwargs)
        else:

            rv = super().jsonify(data, many, *args, **kwargs)
        from flask import request
        if request.method == 'POST':
            return rv, 201
        else:
            return rv,200

    class Meta:
        json_module = _json
        dateformat = '%Y/%m/%d %H:%M:%S'


class PostSchema(ma.Schema):
    max_count = fields.Integer()


class CurrencyQuerySchema(ma.Schema):
    currency = fields.Function(deserialize=lambda obj: Currency.get(Currency.id == obj), load_from='c')
    # currency = fields.Str(load_from='c')


class QuerySchema(CurrencyQuerySchema):
    pass


class CurrencyPostSchema(ma.Schema):
    currency = fields.Function(lambda obj: Currency.get_or_none('CNY'))


class FetchHotProductSchema(PostSchema, CurrencyPostSchema):
    category_id = fields.Integer()
    # class Meta:
    #     fields = ['category_id', 'currency']


class ProductContentSchema(ma.Schema):
    language = fields.Str()
    summary = fields.Str()
    detail = fields.Str()


class CurrencySchema(Schema):
    id = fields.Str()
    screen_name = fields.Str()
    logo = fields.Str()
    # rate = fields.Str()
    # type = fields.Str()
    prefix = fields.Str()
    suffix = fields.Str()


class ProductMetadataSchema(Schema):
    metadata = fields.Method('_write_metadata')

    def _write_metadata(self, data):
        return {
            'id': data.metadata.id,
            'screen_name': data.metadata.screen_name,
            'value_mode': data.metadata.value_mode,
            'value': data.value,
            'extension': data.metadata.extension
        }


class ProductCategorySchema(ma.Schema):
    id = fields.Str()


class ProductBaseSchema(ma.Schema):
    title = fields.Str()
    sub_title = fields.Str()
    id = fields.Str()
    type = fields.Str()
    status = fields.Str()
    cover = fields.Str()
    like_count = fields.Integer()
    click_count = fields.Integer()
    collection_count = fields.Integer()
    # content = fields.Function(
    #     lambda product, context: ProductContentSchema().dump(product.get_content(context.get('language', None)),
    #                                                          many=True).data)

    metadata = fields.Method('_write_metadata')

    def _write_metadata(self, data):
        metadata_map = {}
        for m in data.metadata:
            metadata_map[m.metadata_id] = {
                'screen_name': m.metadata.screen_name,
                'value_mode': m.metadata.value_mode,
                'value': m.value
            }
        return metadata_map


class ProductSchema(ProductBaseSchema):
    content = fields.Method("_write_content")
    category = fields.Nested(ProductCategorySchema)
    offer = fields.Method("_write_offer")

    def _write_offer(self, data):
        offer = data.offer
        if offer:
            price_stat_schema = PriceStatSchema()

            return price_stat_schema.dump(offer).data
        return {}

    def _write_content(self, data):
        language = self.context.get('language', None)
        _content = {}
        schema = ProductContentSchema()
        for content in data.get_content(language):
            _content[content.language] = schema.dump(content).data
        return _content


class HotProductSchema(ProductBaseSchema):
    min_price = fields.Number()
    max_price = fields.Number()


class CountrySchema(Schema):
    id = fields.Str()
    screen_name = fields.Str()
    logo = fields.Str()


class MemberSchema(ma.Schema):
    id = fields.Str()
    screen_name = fields.Str()
    sales_rate = fields.Number()
    head_image = fields.Str()
    buyer_rate = fields.Number()
    deal_count = fields.Number()
    country = fields.Nested(CountrySchema)

    class Meta:
        json_module = _json


class OfferSchema(Success):
    id = fields.Str()
    product = fields.Nested(ProductSchema, only=['id', 'title', 'cover'])
    price = fields.Method(serialize='_tprice')
    member = fields.Nested(MemberSchema, dump_to='dealer', load_from='dealer')
    count = fields.Int()
    sales_count = fields.Int()
    status = fields.Str()

    # remaining = fields.Function(lambda x: int(x.count) - int(x.sales_count))

    def _tprice(self, stock):
        if 'currency' in self.context:
            currency = self.context.get('currency')
            return float(round((stock.price * currency.rate), 2))
        else:
            return float(round(stock.price, 2))

    class Meta:
        json_module = _json


class RecommendStockSchema(Schema):
    stock = fields.Nested(OfferSchema)
    additional = fields.Method('_serialize_additional')

    def _serialize_additional(self, data):

        currency = self.context.get('currency', None)

        if data and 'additional' in data:
            from decimal import Decimal
            for additional in data['additional']:
                if currency:
                    additional['amount'] = float(round(additional['amount'] * currency.rate, 2))
        return data.get('additional', [])


class ProductStockSchema(Schema):
    currency = fields.Nested(CurrencySchema, exclude=['rate', 'type'])
    stock = fields.Nested(OfferSchema, many=True, exclude=['product'])
    recommend = services = fields.Nested(RecommendStockSchema)


class SuccessSchema(ma.Schema):
    success = fields.Bool()

    def __init__(self, nested_cls=None):
        ma.Schema.__init__(self)
        self.nested_cls = nested_cls

    def jsonify(self, obj={}):
        if self.nested_cls:
            obj = self.nested_cls().dump(obj).data
        return super().jsonify({
            'success': True,
            'result': obj
        })

    class Meta:
        fields = ('success', 'result')

    @classmethod
    def create(cls, nested_cls=None):
        return SuccessSchema(nested_cls)


class ServiceSchema(ma.Schema):
    id = fields.Str()
    fee = fields.Method(serialize='_serialize_fee')
    screen_name = fields.Str()
    short_name = fields.Str()
    mode = fields.Str()

    def _serialize_fee(self, service):

        if service.mode == 'fixed':
            if 'currency' in self.context:
                currency = self.context.get('currency')
                return float(round((service.fee * currency.rate), 2))
            else:
                return float(round(service.fee, 2))
        elif service.mode == 'rate':
            if 'price' in self.context:
                price = self.context.get('price')
                fee = price * service.fee
                if 'currency' in self.context:
                    currency = self.context.get('currency')
                    return float(round((fee * currency.rate), 2))
                else:
                    return float(round(fee, 2))

    class Meta:
        json_module = _json


class ShopCharItemStockSchema(ma.Schema):
    id = fields.Str()
    dealer = fields.Nested(MemberSchema)
    price = fields.Number()
    remaining = fields.Int()


def _calc_service_fee(service, price, currency):
    from decimal import Decimal
    rate = currency.rate if currency else Decimal.from_float(1.00)
    if service.mode == 'fixed':
        return float(round(service.fee * rate, 2))
    elif service.mode == 'rate':
        return float(round((service.fee * price) * rate, 2))


class ShopCharItemSchema(ma.Schema):
    product = fields.Nested(ProductSchema, only=('id', 'title', 'sub_title', 'cover', 'status'))
    stock = fields.Nested(OfferSchema, exclude=['product', 'count', 'sales_count'])
    count = fields.Int()
    id = fields.Int()
    shop_cart_id = fields.Str()
    additives = fields.Method(serialize='_serialize_additives')

    def _serialize_additives(self, obj):
        additives = obj.additives
        if not isinstance(additives, (tuple, list)):
            additives = list(additives)

        for additive in additives:

            type = additive.type
            if type == 'service':
                from ..models.service import Service
                service = Service.get_or_none(Service.id == additive.ref_id)
                if service:
                    additive.amount = _calc_service_fee(service, obj.stock.price, self.context.get('currency'))
        if additives:
            schema = CartAdditiveSchema(many=True)
            return schema.dump(additives).data

    class Meta:
        json_module = _json


class CartAdditiveSchema(ma.Schema):
    id = fields.Int()
    title = fields.Str()
    ref_id = fields.Str()
    required = fields.Str()
    amount = fields.Number()


cart_item_schema = ShopCharItemSchema()
cart_items_schema = ShopCharItemSchema(many=True)
query_params = QuerySchema()
fetch_hot_product_params = FetchHotProductSchema()
member_schema = MemberSchema()
service_schema = ServiceSchema()
success_schema = SuccessSchema()
currency_schema = CurrencySchema(many=False)
hot_product_schema = HotProductSchema(many=True)


class ErrorSchema(ma.Schema):
    code = fields.Int()
    message = fields.Str()
    success = fields.Bool()

    def __init__(self, code, message):
        ma.Schema.__init__(self)
        self.code = code
        self.message = message

    def jsonify(self):
        return super().jsonify({
            'code': self.code,
            'message': self.message,
            'success': False
        })


###################################PAYMENT OBJECT######################################

class GatewaySchema(ma.Schema):
    id = fields.Str()
    screen_name = fields.Str()
    logo = fields.Str()
    type = fields.Str()


class FundChannelSchema(ma.Schema):
    id = fields.Str()
    name = fields.Str()
    fee_rate = fields.Number()
    desc = fields.Str()
    gateway = fields.Nested(GatewaySchema)


fund_channels_schema = FundChannelSchema(many=True)


###################################ORDER OBJECT#########################################




class OrderDetailSchema(Success):
    id = fields.Int()
    count = fields.Int()
    amount = fields.Number()
    product = fields.Nested(ProductBaseSchema)
    discount_amount = fields.Number()
    discount_method = fields.Str()
    discount_code = fields.Str()
    real_amount = fields.Number()


class OrderSchema(Success):
    id = fields.Int()
    amount = fields.Number()
    real_amount = fields.Number()
    discount_amount = fields.Number()
    status = fields.Str()
    discount_method = fields.Str()
    discount_code = fields.Str()
    fee = fields.Number()
    real_fee = fields.Number()
    payment_time = fields.DateTime()
    payment_method = fields.Str()
    created_at = fields.DateTime()
    currency = fields.Nested(CurrencySchema)
    updated_at = fields.DateTime()
    expire_time = fields.DateTime()
    settle_time = fields.DateTime()

    details = fields.List(fields.Nested(OrderDetailSchema))


order_schema = OrderSchema(many=False)
orders_schema = OrderSchema(many=True)


class CheckOutSchema(Success):
    id = fields.Str(dump_to='payment_seq')
    post_url = fields.Str()
    approval = fields.Dict()


check_out_schema = CheckOutSchema(many=False)


class AccountSchema(Success):
    id = fields.Str()
    member = fields.Nested(MemberSchema)
    currency = fields.Nested(CurrencySchema)
    balance = fields.Number()
    frozen_amount = fields.Number()
    pending_amount = fields.Number()
    status = fields.Str()
    remark = fields.Str()
    updated_at = fields.DateTime()
    created_at = fields.DateTime()
    reward_amount = fields.Number()


####################################offer###############################################
class OfferFilterSchema(Success):
    pagination = fields.Dict()
    currency = fields.Nested(CurrencySchema, only=['prefix'])
    items = fields.Nested(OfferSchema, many=True, exclude=['member'])


class PriceStatSchema(Success):
    minimum = fields.Number()
    maximum = fields.Number()
    average = fields.Number()


class OfferPriceStateSchema(Success):
    currency = fields.Nested(CurrencySchema)
    data = fields.Method(serialize='_write_data')

    def _write_data(self, obj):
        data = obj.get('data')
        price_stat_schema = PriceStatSchema()
        price_stat_schema.context = self.context
        for k, v in data.items():
            data[k] = price_stat_schema.dump(v).data
        return data


offer_price_state_schema = OfferPriceStateSchema()
offer_filter_schema = OfferFilterSchema()
offer_schema = OfferSchema(many=False)


###################################Member ###############################################

class MemberSummarySchema(Success):
    account = fields.Nested(AccountSchema, exclude=['member', ])
    sales = fields.Dict()


member_summary_schema = MemberSummarySchema(many=False)


class DeliveryInfo(Success):
    id = fields.Int()
    type = fields.Str()
    carrier = fields.Str()
    trace_no = fields.Str()
    extension = fields.Function(lambda x: _json.dump(x))
    deliver_time = fields.DateTime()
    status = fields.Function(lambda x: _json.dump(x))


class AccountHistory(Success):
    id = fields.Int()
    # account = fields.Nested(AccountSchema)
    direction = fields.Str()
    amount = fields.Number()
    remark = fields.Str()
    type = fields.Str()
    order = fields.Nested(OrderSchema,exclude=('details',))
    settle_time = fields.DateTime()
    created_at = fields.DateTime()
    updated_at = fields.DateTime()
    settled_at = fields.DateTime()
    status = fields.Str()


account_histories_resp = AccountHistory(many=True)


success = Success()
